package com.kzj.domain.bp;

import com.kzj.common.constant.GlobalConstant;
import com.kzj.common.exception.MyException;

import lombok.Data;
import org.thymeleaf.util.StringUtils;


import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.*;

/**
 * 算式
 */
@Data
public class EquationBP {
    private String content;
    public EquationBP(String content){
        this.content=content;
    }
    static int[][] table={
            {1,1,-1,-1,-1,1,1},
            {1,1,-1,-1,-1,1,1},
            {1,1,1,1,-1,1,1},
            {1,1,1,1,-1,1,1},
            {-1,-1,-1,-1,-1,0,2},
            {1,1,1,1,2,1,1},
            {-1,-1,-1,-1,-1,2,0}
    };
    static Map<String,Integer> map=new HashMap<String,Integer>(){{
        put("+",0);
        put("-",1);
        put("×",2);
        put("÷",3);
        put("(",4);
        put(")",5);
        put("#",6);
    }};
    public static String calculateResult(EquationBP equationBP)
    {
        return EquationBP.calculateResult(equationBP.getContent());
    }
    public static String calculateResult(String content)
    {
        //处理多余的空格
        content=content.replace(" ","");
        //将负数-n转化为0-n
        if(content.substring(0,1).equals(GlobalConstant.SYMBOL_SUB))
        {
            content="0"+content;
        }
        content=content.replace("("+GlobalConstant.SYMBOL_SUB,"(0"+GlobalConstant.SYMBOL_SUB);

        content=content+"#";
        Stack<BigDecimal> numberStack=new Stack<>();
        Stack<String> symbolStack=new Stack<>();
        symbolStack.push("#");
        for(int i=0;i<content.length();i++)
        {
            String str=content.substring(i,i+1);
            //数字入栈
            if(str.matches("[0-9]"))
            {
                i++;
                String next=content.substring(i,i+1);
                while(next.matches("[0-9.]"))
                {
                    str+=next;
                    i++;
                    next=content.substring(i,i+1);
                }
                i--;
                BigDecimal decimal = new BigDecimal(str);
                numberStack.push(decimal);
            }
            //运算符处理
            else if(str.matches("[()+×÷#-]"))
            {
                int flag=table[map.get(symbolStack.peek())][map.get(str)];
                while(flag==1)
                {
                    BigDecimal num2=numberStack.pop();
                    BigDecimal num1=numberStack.pop();
                    String symbol=symbolStack.pop();
                    //加法
                    if(symbol.equals(GlobalConstant.SYMBOL_ADD))
                    {
                        num1=num1.add(num2);
                    }
                    //减法
                    else if(symbol.equals(GlobalConstant.SYMBOL_SUB))
                    {
                        num1=num1.subtract(num2);
                    }
                    //乘法
                    else if(symbol.equals(GlobalConstant.SYMBOL_MUL))
                    {
                        num1=num1.multiply(num2);
                    }
                    //除法
                    else if(symbol.equals(GlobalConstant.SYMBOL_DIV))
                    {
                        num1=num1.divide(num2,GlobalConstant.BIG_DECIMAL_SCALE,GlobalConstant.BIG_DECIMAL_ROUNDING_MODE);
                    }else{
                        throw new MyException(GlobalConstant.EQUATION_FAULTY);
                    }
                    numberStack.push(num1);
                    flag=table[map.get(symbolStack.peek())][map.get(str)];
                }
                if(flag==0)
                {
                    symbolStack.pop();
                }
                else if(flag==-1)
                {
                    symbolStack.push(str);
                }
                else{
                    throw new MyException(GlobalConstant.EQUATION_FAULTY);
                }
            }
            else{
                throw new MyException(GlobalConstant.EQUATION_FIND_UNKNOWN_CHAR);
            }

        }
        BigDecimal pop = numberStack.pop();
        return pop.toPlainString();
    }
    public static void main(String[] args) {
        int num=10000;
        for(int i=0;i<num;i++)
        {
            EquationBP equationBP = EquationBP.generateEquation(100, -100, 4, 2);
            System.out.println("生成的算式： "+ equationBP.toString());

            String s = EquationBP.calculateResult(equationBP.toString().replace("="," "));
            System.out.println("算式的结果： "+s);
        }

    }

    @Override
    public String toString() {
        List<String> list=new ArrayList<>();
        for(int i=0;i<this.content.length();i++)
        {
            String str=content.substring(i,i+1);
            if(str.matches("[0-9.]"))
            {
                i++;
                String next;
                if(i<content.length())
                    next=content.substring(i,i+1);
                else next="f";
                while(next.matches("[0-9.]"))
                {
                    str+=next;
                    i++;
                    if (i<content.length())
                        next=content.substring(i,i+1);
                    else next="f";
                }
                i--;
                list.add(str);
            }
            else if(str.matches("[(]"))
            {
                i++;
                String sub=content.substring(i,i+1);
                if(sub.equals(GlobalConstant.SYMBOL_SUB))
                {
                    str+=sub;
                    i++;
                    String next=content.substring(i,i+1);
                    while(next.matches("[0-9.]"))
                    {
                        str+=next;
                        i++;
                        next=content.substring(i,i+1);
                    }
                    if(!next.equals(")"))
                    {
                        throw new MyException(GlobalConstant.EQUATION_FAULTY);
                    }
                    str+=next;
                    list.add(str);
                }
                else{
                    i--;
                    list.add(str);
                }
            }
            else if(str.matches("[()+×÷#-]"))
            {
                list.add(str);
            }
            else {
                throw new MyException(GlobalConstant.EQUATION_FAULTY);
            }
        }
        list.add("=");
        String join = StringUtils.join(list, " ");
        return join;
    }
    public static String getTemplate(Integer decimalPlace)
    {
        String template="#";
        if(decimalPlace.compareTo(0)>0)
        {
            template+=".";
            for(int i=0;i<decimalPlace;i++)
            {
                template+="#";
            }
        }
        return template;
    }

    /**
     * 随机生成upperLimit-downLimit的算式
     * @param upperLimit 计算数的上线
     * @param downLimit 计算数的下线
     * @param number 位数
     * @param decimalPlace 保留几位小数
     * @param symbol 符号
     * @return
     */
    public static EquationBP generateBaseEquation(Integer upperLimit , Integer downLimit, Integer number, Integer decimalPlace, String symbol)
    {
        if(number<2)
        {
            throw new MyException(GlobalConstant.GENERATE_CONDITION_FAULTY);
        }
        String result="";
        Random random = new Random();

        DecimalFormat decimalFormat=new DecimalFormat(getTemplate(random.nextInt(decimalPlace+1)));
        double v = random.nextDouble()*(upperLimit-downLimit)+downLimit;
        String str=decimalFormat.format(v);
        if(v<0)
        {
            str="("+str+")";
        }
        result+=str;

        for(int i=1;i<number;i++)
        {
            decimalFormat=new DecimalFormat(getTemplate(random.nextInt(decimalPlace+1)));
            v = random.nextDouble()*(upperLimit-downLimit)+downLimit;
            str=decimalFormat.format(v);
            if(v<0)
            {
                str="("+str+")";
            }
            result+=symbol+str;
        }
        return new EquationBP(result);
    }
    public static EquationBP generateAdditionEquation(Integer upperLimit , Integer downLimit, Integer number, Integer decimalPlace)
    {
        return generateBaseEquation(upperLimit,downLimit,number,decimalPlace,GlobalConstant.SYMBOL_ADD);
    }


    public static EquationBP generateSubstractEquation(Integer upperLimit , Integer downLimit, Integer number, Integer decimalPlace)
    {
        return generateBaseEquation(upperLimit,downLimit,number,decimalPlace,GlobalConstant.SYMBOL_SUB);
    }

    public static EquationBP generateMulEquation(Integer upperLimit , Integer downLimit, Integer number, Integer decimalPlace)
    {
        return generateBaseEquation(upperLimit,downLimit,number,decimalPlace,GlobalConstant.SYMBOL_MUL);
    }
    public static EquationBP generateDivEquation(Integer upperLimit , Integer downLimit, Integer number, Integer decimalPlace)
    {
        try{
            EquationBP equationBP=generateBaseEquation(upperLimit,downLimit,number,decimalPlace,GlobalConstant.SYMBOL_DIV);
            calculateResult(equationBP);
            return equationBP;
        }catch (ArithmeticException e){
            return generateDivEquation(upperLimit,downLimit,number,decimalPlace);
        }
    }
    public static EquationBP generateEquationByGrandeOne(Integer upperLimit , Integer downLimit, Integer number, Integer decimalPlace)
    {
            if(number<2)
            {
                throw new MyException(GlobalConstant.GENERATE_CONDITION_FAULTY);
            }

            String result="";
            Random random = new Random();

            DecimalFormat decimalFormat=new DecimalFormat(getTemplate(random.nextInt(decimalPlace+1)));
            double v = random.nextDouble()*(upperLimit-downLimit)+downLimit;
            String str=decimalFormat.format(v);
            if(v<0)
            {
                str="("+str+")";
            }
            result+=str;
            for(int i=1;i<number;i++)
            {
                decimalFormat=new DecimalFormat(getTemplate(random.nextInt(decimalPlace+1)));
                v = random.nextDouble()*(upperLimit-downLimit)+downLimit;
                str=decimalFormat.format(v);
                if(v<0)
                {
                    str="("+str+")";
                }
                int tag = random.nextInt(2);
                String symbol="";
                if(tag==0)
                {
                    symbol=GlobalConstant.SYMBOL_ADD;
                }else if(tag==1)
                {
                    symbol=GlobalConstant.SYMBOL_SUB;
                }
                result+=symbol+str;
            }
            result=addBrackets(result);
            calculateResult(result);
            return new EquationBP(result);
    }
    public static EquationBP generateEquation(Integer upperLimit , Integer downLimit, Integer number, Integer decimalPlace)
    {
        try{
            if(number<2)
            {
                throw new MyException(GlobalConstant.GENERATE_CONDITION_FAULTY);
            }

            String result="";
            Random random = new Random();

            DecimalFormat decimalFormat=new DecimalFormat(getTemplate(random.nextInt(decimalPlace+1)));
            double v = random.nextDouble()*(upperLimit-downLimit)+downLimit;
            String str=decimalFormat.format(v);
            if(v<0)
            {
                str="("+str+")";
            }
            result+=str;
            for(int i=1;i<number;i++)
            {
                decimalFormat=new DecimalFormat(getTemplate(random.nextInt(decimalPlace+1)));
                v = random.nextDouble()*(upperLimit-downLimit)+downLimit;
                str=decimalFormat.format(v);
                if(v<0)
                {
                    str="("+str+")";
                }
                int tag = random.nextInt(4);
                String symbol="";
                if(tag==0)
                {
                    symbol=GlobalConstant.SYMBOL_ADD;
                }else if(tag==1)
                {
                    symbol=GlobalConstant.SYMBOL_SUB;
                }else if(tag==2)
                {
                    symbol=GlobalConstant.SYMBOL_MUL;
                }else if(tag==3)
                {
                    symbol=GlobalConstant.SYMBOL_DIV;
                }
                result+=symbol+str;
            }
            result=addBracketsPlus(result);
            calculateResult(result);
            return new EquationBP(result);
        }catch (ArithmeticException e){
            return generateEquation(upperLimit,downLimit,number,decimalPlace);
        }

    }
    public static String addBrackets(String content)
    {
        List<String> list=new ArrayList<>();
        int num=0;
        for(int i=0;i<content.length();i++)
        {
            String str=content.substring(i,i+1);
            if(str.matches("[0-9.]"))
            {
                i++;
                String next;
                if(i<content.length())
                    next=content.substring(i,i+1);
                else next="f";
                while(next.matches("[0-9.]"))
                {
                    str+=next;
                    i++;
                    if(i<content.length())
                    next=content.substring(i,i+1);
                    else next="f";
                }
                i--;
                list.add(str);
            }
            else if(str.matches("[(]"))
            {
                i++;
                String sub=content.substring(i,i+1);
                if(sub.equals(GlobalConstant.SYMBOL_SUB))
                {
                    str+=sub;
                    i++;
                    String next=content.substring(i,i+1);
                    while(next.matches("[0-9.]"))
                    {
                        str+=next;
                        i++;
                        next=content.substring(i,i+1);
                    }
                    if(!next.equals(")"))
                    {
                        throw new MyException(GlobalConstant.EQUATION_FAULTY);
                    }
                    str+=next;
                    list.add(str);
                }
                else{
                    i--;
                    list.add(str);
                }
            }
            else if(str.matches("[()+×÷#-]"))
            {
                if(str.matches("[×÷]"))
                    num++;
                list.add(str);
            }
            else {
                throw new MyException(GlobalConstant.EQUATION_FAULTY);
            }
        }
        if(num==0)
            return content;
        Random random=new Random();
        double probability=1;
        if(num==1)
        {
            probability=2.0/3;
        }
        else{
            probability=1.0/num;
        }
        int flag=0;
        for(int i=0;i<list.size();i++)
        {
            String str=list.get(i);
            if(str.matches("[×÷]")&&random.nextDouble()<probability&&i>2&&list.get(i-2).matches("[+-]"))
            {
               flag=i;
               break;
            }
        }
        if(flag==0)
            return content;
        else{
            list.add(flag,")");
            list.add(flag-3,"(");
        }
        String join = StringUtils.join(list, "");
        return join;
    }
    public static String addBracketsPlus(String content)
    {
//        System.out.println("addBracketsPlus content->"+content);
        List<String> list=new ArrayList<>();
        int num=0;
        for(int i=0;i<content.length();i++)
        {
            String str=content.substring(i,i+1);
            if(str.matches("[0-9.]"))
            {
                i++;
                String next;
                if(i<content.length())
                    next=content.substring(i,i+1);
                else next="f";
                while(next.matches("[0-9.]"))
                {
                    str+=next;
                    i++;
                    if(i<content.length())
                        next=content.substring(i,i+1);
                    else next="f";
                }
                i--;
                list.add(str);
            }
            else if(str.matches("[(]"))
            {
               int amount=1;
               String next;
               i++;
               if(i<content.length())
                   next=content.substring(i,i+1);
               else next="f";
               while(amount>0)
               {
                   str+=next;
                   i++;
                   if(i<content.length())
                       next=content.substring(i,i+1);
                   else throw new MyException(GlobalConstant.EQUATION_FAULTY);
                   if(next.equals(")"))
                       amount--;
                   if(next.equals("("))
                       amount++;
               }
                str+=next;
               list.add(str);
            }
            else if(str.matches("[()+×÷#-]"))
            {
                if(str.matches("[×÷]"))
                    num++;
                list.add(str);
            }
            else {
                System.out.println("str:"+str);
                throw new MyException(GlobalConstant.EQUATION_FAULTY);
            }
        }
        if(num==0)
            return content;
        Random random=new Random();
        double probability=1;
        if(num==1)
        {
            probability=2.0/3;
        }
        else{
            probability=1.0/num;
        }
        int flag=0;
        for(int i=0;i<list.size();i++)
        {
            String str=list.get(i);
            if(str.matches("[×÷]")&&random.nextDouble()<probability&&i>2&&list.get(i-2).matches("[+-]"))
            {
                flag=i;
                break;
            }
        }
        if(flag==0)
            return content;
        else{
            list.add(flag,")");
            list.add(flag-3,"(");
        }
        String join = StringUtils.join(list, "");
//        System.out.println("添加括号成功：》》》》》》》》》》》》》》》》》》》》》》》》》》》");
//        System.out.println("（）-》"+join);
        return join;
    }

    /**
     * 判断与目标Equation是否相同
     * @param obj 算式
     * @return ture:算式完全相同 false:算式不同
     */
    public boolean equals(EquationBP obj) {
        if(obj==null)
            return false;
        return this.content.equals(obj.content);

    }
}
